package cn.song.freight.controller;

import cn.hutool.core.util.StrUtil;
import cn.song.freight.annotation.OperationLog;
import cn.song.freight.domain.dto.*;
import cn.song.freight.domain.entity.AreaDO;
import cn.song.freight.domain.entity.PricingAreaMappingDO;
import cn.song.freight.domain.entity.StrategyAreaPricingDO;
import cn.song.freight.domain.result.R;
import cn.song.freight.domain.vo.StrategyAreaPricingListVO;
import cn.song.freight.domain.vo.WeightSegmentRuleVO;
import cn.song.freight.enums.ModuleType;
import cn.song.freight.enums.OperationType;
import cn.song.freight.mapper.StrategyAreaPricingMapper;
import cn.song.freight.service.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 策略区域定价控制器（V3重构版本）
 * <pre>
 * 该控制器负责管理运费策略的区域定价配置，是V3版本架构重构的核心控制器之一。
 * V3版本重构后，实现了定价配置与区域映射的分离，支持更灵活的业务场景。
 *
 * 核心功能：
 * 1. 定价配置管理：创建、更新、删除和查询策略的定价配置
 * 2. 区域映射管理：管理定价配置与区域的多对多关联关系
 * 3. 统一配置保存：支持定价配置、区域映射、重量规则的原子化批量操作
 * 4. 配置查询回显：支持编辑时的数据回显和详情查询
 *
 * V3架构特性：
 * - 定价配置独立化：定价配置不再直接关联区域，通过映射表实现多对多关系
 * - 原子化事务操作：通过统一服务实现多表操作的事务安全
 * - 灵活的配置复用：同一定价配置可应用于多个区域
 * - 完整的业务逻辑封装：复杂逻辑已移至Service层，Controller专注于接口调用
 *
 * 数据流向：
 * 前端请求 → Controller接口 → 统一服务层 → 具体业务服务 → 数据持久层
 *
 * 使用场景：
 * - 多区域差异化定价：为不同区域设置不同的运费标准
 * - 促销活动支持：临时调整特定区域的运费策略
 * - 业务规则管理：灵活配置首重续重、体积重等计费参数
 * - 批量配置操作：支持一次性配置多个区域的定价规则
 *
 * @author SongRenShuo
 * @version 3.0.0
 * @since 2025-08-26
 * </pre>
 */
@Slf4j
@Validated
@RestController
@RequestMapping("/strategy-area-pricing")
@Api(tags = "策略区域定价管理")
@RequiredArgsConstructor
public class StrategyAreaPricingController {

    private final StrategyConfigUnifiedService strategyConfigUnifiedService;

    private final PricingAreaMappingService pricingAreaMappingService;

    private final StrategyAreaPricingService strategyAreaPricingService;

    private final AreaService areaService;

    private final StrategyAreaPricingMapper strategyAreaPricingMapper;

    private final WeightSegmentRuleService weightSegmentRuleService;

    /**
     * V3统一配置保存接口 - 简化版本
     * <pre>
     * 根据需求一调整的新版本接口，采用简化的嵌套参数结构。
     * 主要变更：
     * 1. 将新增、修改、删除的配置数据整合到统一列表中
     * 2. 通过ID是否为null判断操作类型：null=新增，非null=更新
     * 3. 数据库中存在但请求中不存在的记录将被删除
     * 4. 采用直接嵌套结构，不再使用临时ID关联
     *
     * 处理逻辑：
     * - 重量规则列表中的每个规则可以包含多个区域配置
     * - 区域配置直接嵌套在重量规则内，无需临时ID关联
     * - 后端根据ID的存在性自动判断增删改操作
     *
     * @param requestDTO 简化结构的统一配置请求对象
     * @return 操作结果
     * </pre>
     */
    @PostMapping("/save-all-config-v2")
    @ApiOperation(value = "V3统一配置保存 - 简化版本", notes = "使用简化的嵌套结构进行统一配置保存")
    @OperationLog(
            moduleType = ModuleType.UNIFIED_CONFIG,
            operationType = OperationType.BATCH_UPDATE,
            description = "V3统一配置保存 - 简化版本",
            businessNameField = "strategyId",
            businessIdField = "strategyId"
    )
    public R<Boolean> saveAllConfigV2(@Valid @RequestBody StrategyConfigUnifiedSaveRequestDTO requestDTO) {
        log.info("🚀 收到V3统一保存配置请求（极简优化版），策略ID：{}，操作人：{}",
                requestDTO.getStrategyId(), requestDTO.getOperator());
        return strategyConfigUnifiedService.processAllConfigChangesV2(requestDTO);
    }

    /**
     * 查询策略区域定价配置列表
     * <pre>
     * 根据策略ID查询该策略下的所有定价配置，并关联查询每个定价配置
     * 对应的区域映射信息，用于前端列表展示和配置管理。
     *
     * 查询逻辑：
     * 1. 根据策略ID查询所有定价配置
     * 2. 对每个定价配置查询关联的区域ID列表
     * 3. 根据区域ID查询区域详细信息
     * 4. 格式化区域名称（省-市-区）
     * 5. 组装返回数据
     *
     * 返回信息包含：
     * - 定价配置基本信息（名称、描述、计费参数等）
     * - 关联的区域ID列表
     * - 格式化的区域名称列表
     * - 关联区域数量统计
     *
     * @param queryDTO 查询条件，主要包含策略ID
     * @return 定价配置列表，包含区域映射信息
     * </pre>
     */
    @PostMapping("/list")
    @ApiOperation(value = "查询策略区域定价列表", notes = "查询策略的定价配置及其关联的区域映射信息")
    public R<List<StrategyAreaPricingListVO>> list(@Valid @RequestBody StrategyAreaPricingQueryDTO queryDTO) {
        log.info("查询策略区域定价列表，查询条件：{}", queryDTO);

        Long strategyId = queryDTO.getStrategyId();
        List<StrategyAreaPricingDO> pricingConfigs = strategyAreaPricingService.getByStrategyId(strategyId);

        if (pricingConfigs.isEmpty()) {
            return R.success(new ArrayList<>());
        }

        // 性能优化：批量查询所有定价配置的区域映射关系，避免N+1查询问题
        List<Long> allPricingIds = pricingConfigs.stream()
                .map(StrategyAreaPricingDO::getId)
                .collect(Collectors.toList());

        List<PricingAreaMappingDO> allMappings = pricingAreaMappingService.listByPricingIds(allPricingIds);
        log.info("📊 性能优化：批量查询{}个定价配置的映射关系，共{}个映射",
                allPricingIds.size(), allMappings.size());

        // 构建定价配置ID到区域ID列表的映射
        Map<Long, List<Long>> pricingToAreaIdsMap = allMappings.stream()
                .collect(Collectors.groupingBy(
                        PricingAreaMappingDO::getPricingId,
                        Collectors.mapping(PricingAreaMappingDO::getAreaId, Collectors.toList())
                ));

        // 批量查询所有相关的区域信息，避免在循环中逐个查询
        Set<Long> allAreaIds = allMappings.stream()
                .map(PricingAreaMappingDO::getAreaId)
                .collect(Collectors.toSet());

        Map<Long, AreaDO> areaMap = allAreaIds.isEmpty() ?
                new HashMap<>(16) :
                areaService.listByIds(new ArrayList<>(allAreaIds)).stream()
                        .collect(Collectors.toMap(AreaDO::getId, Function.identity()));

        log.info("📊 批量查询{}个区域信息", areaMap.size());


        List<WeightSegmentRuleVO> allRules = weightSegmentRuleService.listByStrategyId(strategyId);
        // 构建定价配置ID到重量区间规则列表的映射
        Map<Long, List<WeightSegmentRuleVO>> pricingIdTorulesMap = allRules.stream()
                .collect(Collectors.groupingBy(
                        WeightSegmentRuleVO::getPricingId, Collectors.toList()
                ));

        List<StrategyAreaPricingListVO> result = new ArrayList<>();
        // 在内存中完成数据组装，无需额外的数据库查询
        for (StrategyAreaPricingDO pricing : pricingConfigs) {
            List<Long> areaIds = pricingToAreaIdsMap.getOrDefault(pricing.getId(), new ArrayList<>());
            List<WeightSegmentRuleVO> weightSegmentRules = pricingIdTorulesMap.getOrDefault(pricing.getId(), new ArrayList<>());

            List<String> areaNames = areaIds.stream()
                    .map(areaMap::get)
                    .filter(Objects::nonNull)
                    .map(this::formatAreaName)
                    .collect(Collectors.toList());

            StrategyAreaPricingListVO item = new StrategyAreaPricingListVO();
            BeanUtils.copyProperties(pricing, item);
            item.setAreaIds(areaIds);
            item.setAreaNames(areaNames);
            item.setAreaCount(areaNames.size());
            item.setWeightSegmentRules(weightSegmentRules);
            result.add(item);
        }

        return R.success(result);
    }

    /**
     * 添加策略区域定价配置
     * <pre>
     * 创建新的定价配置并建立与区域的映射关系。
     * 该操作涉及两个步骤：
     * 1. 创建定价配置记录
     * 2. 批量创建定价配置与区域的映射关系
     *
     * 处理流程：
     * 1. 构建定价配置DTO对象
     * 2. 调用服务层创建定价配置
     * 3. 获取新创建的定价配置ID
     * 4. 批量创建区域映射关系
     * 5. 返回操作结果
     *
     * 数据转换：
     * - Double类型参数转换为BigDecimal确保精度
     * - 前端选中的区域ID列表转换为映射关系
     *
     * @deprecated 使用 V3统一配置保存接口
     * @see StrategyAreaPricingController#saveAllConfigV2(StrategyConfigUnifiedSaveRequestDTO requestDTO)
     * @param addDTO 添加请求对象，包含定价配置信息和选中的区域ID列表
     * @return 操作结果
     * </pre>
     */
    @PostMapping("/add")
    @ApiOperation(value = "添加策略区域定价", notes = "创建定价配置并建立区域映射关系")
    @OperationLog(
            moduleType = ModuleType.AREA_PRICING,
            operationType = OperationType.INSERT,
            description = "添加策略区域定价配置",
            businessNameField = "name",
            businessIdField = "strategyId"
    )
    public R<Boolean> add(@Valid @RequestBody StrategyAreaPricingAddDTO addDTO) {
        log.info("添加策略区域定价，数据：{}", addDTO);

        StrategyAreaPricingDTO pricingDTO = new StrategyAreaPricingDTO();
        pricingDTO.setStrategyId(addDTO.getStrategyId());
        pricingDTO.setName(addDTO.getName());
        pricingDTO.setDescription(addDTO.getDescription());
        pricingDTO.setFirstWeightKg(getBigDecimal(addDTO.getFirstWeightKg()));
        pricingDTO.setFirstWeightFee(getBigDecimal(addDTO.getFirstWeightFee()));
        pricingDTO.setAdditionalWeightKg(getBigDecimal(addDTO.getAdditionalWeightKg()));
        pricingDTO.setAdditionalWeightFee(getBigDecimal(addDTO.getAdditionalWeightFee()));
        pricingDTO.setVolumetricWeightRatio(getBigDecimal(addDTO.getVolumetricWeightRatio()));
        pricingDTO.setOperator(addDTO.getOperator());

        R<Long> addResult = strategyAreaPricingService.addPricingConfig(pricingDTO);
        if (!addResult.isCode()) {
            return R.failed("创建定价配置失败：" + addResult.getMsg());
        }

        Long pricingId = addResult.getData();
        PricingAreaMappingBatchDTO batchDTO = new PricingAreaMappingBatchDTO();
        batchDTO.setPricingId(pricingId);
        batchDTO.setAreaIds(addDTO.getSelectedAreaIds());
        batchDTO.setOperator(addDTO.getOperator());

        boolean result = pricingAreaMappingService.batchAdd(batchDTO);
        return R.isSuccess(result);
    }

    /**
     * 更新策略区域定价配置
     * <pre>
     * 更新现有的定价配置信息，包括名称、描述、计费参数等。
     * 注意：此接口仅更新定价配置本身，不处理区域映射关系的变更。
     * 区域映射关系的变更通过统一配置保存接口处理。
     *
     * 更新流程：
     * 1. 验证定价配置是否存在
     * 2. 构建更新DTO对象
     * 3. 保留策略ID（不允许修改）
     * 4. 调用服务层执行更新操作
     * 5. 返回更新结果
     *
     * 数据处理：
     * - 从现有记录中获取策略ID，确保数据一致性
     * - Double类型转换为BigDecimal保证计算精度
     * - 保留操作人信息用于审计
     *
     * @deprecated 使用 V3统一配置保存接口
     * @see StrategyAreaPricingController#saveAllConfigV2(StrategyConfigUnifiedSaveRequestDTO requestDTO)
     * @param updateDTO 更新请求对象，包含要更新的定价配置信息
     * @return 更新操作结果
     * </pre>
     */
    @PostMapping("/update")
    @ApiOperation(value = "更新策略区域定价", notes = "更新定价配置信息（不包含区域映射变更）")
    @OperationLog(
            moduleType = ModuleType.AREA_PRICING,
            operationType = OperationType.UPDATE,
            description = "更新策略区域定价配置",
            businessNameField = "name",
            businessIdField = "id"
    )
    public R<Boolean> update(@Valid @RequestBody StrategyAreaPricingUpdateDTO updateDTO) {
        log.info("更新策略区域定价，数据：{}", updateDTO);

        StrategyAreaPricingDO existing = strategyAreaPricingMapper.selectById(updateDTO.getId());
        if (existing == null) {
            return R.failed("指定的定价配置不存在");
        }

        StrategyAreaPricingDTO updatePricingDTO = new StrategyAreaPricingDTO();
        updatePricingDTO.setStrategyId(existing.getStrategyId());
        updatePricingDTO.setName(updateDTO.getName());
        updatePricingDTO.setDescription(updateDTO.getDescription());
        updatePricingDTO.setFirstWeightKg(getBigDecimal(updateDTO.getFirstWeightKg()));
        updatePricingDTO.setFirstWeightFee(getBigDecimal(updateDTO.getFirstWeightFee()));
        updatePricingDTO.setAdditionalWeightKg(getBigDecimal(updateDTO.getAdditionalWeightKg()));
        updatePricingDTO.setAdditionalWeightFee(getBigDecimal(updateDTO.getAdditionalWeightFee()));
        updatePricingDTO.setVolumetricWeightRatio(getBigDecimal(updateDTO.getVolumetricWeightRatio()));
        updatePricingDTO.setOperator(updateDTO.getOperator());
        updatePricingDTO.setId(updateDTO.getId());


        return strategyAreaPricingService.updatePricingConfig(updatePricingDTO);

    }


    /**
     * 删除策略区域定价配置
     * <pre>
     * 删除指定的定价配置及其相关的所有关联数据。
     * 由于数据库设置了级联删除约束，删除定价配置时会自动删除：
     * - 相关的区域映射记录
     * - 相关的重量规则记录
     *
     * 删除检查：
     * - 验证定价配置是否存在
     * - 检查是否有业务数据依赖（可扩展）
     * - 记录删除操作日志用于审计
     *
     * 安全性考虑：
     * - 支持软删除模式（可配置）
     * - 记录删除操作的用户和时间
     * - 重要数据删除前可增加二次确认
     *
     * @deprecated 使用 V3统一配置保存接口
     * @see StrategyAreaPricingController#saveAllConfigV2(StrategyConfigUnifiedSaveRequestDTO requestDTO)
     * @param deleteDTO 删除请求对象，包含要删除的定价配置ID
     * @return 删除操作结果
     * </pre>
     */
    @PostMapping("/delete")
    @ApiOperation(value = "删除策略区域定价", notes = "删除定价配置及其所有关联数据（级联删除）")
    @OperationLog(
            moduleType = ModuleType.AREA_PRICING,
            operationType = OperationType.DELETE,
            description = "删除策略区域定价配置",
            businessIdField = "id"
    )
    public R<Boolean> delete(@Valid @RequestBody BaseIdDTO deleteDTO) {
        log.info("删除策略区域定价，参数：{}", deleteDTO);

        return strategyAreaPricingService.deletePricingConfig(deleteDTO.getId());
    }

    /**
     * 根据ID获取策略区域定价配置详情
     * <pre>
     * 查询指定定价配置的详细信息，主要用于编辑时的数据回显功能。
     * 返回的数据包含定价配置的所有字段和关联的区域ID列表。
     *
     * 查询内容：
     * 1. 定价配置基本信息（名称、描述、创建时间等）
     * 2. 计费参数（首重、续重、体积重比例等）
     * 3. 关联的区域ID列表（用于前端区域选择器回显）
     *
     * 数据转换：
     * - BigDecimal转换为Double以适配前端数值输入框
     * - 构建响应DTO确保数据结构清晰
     *
     * 使用场景：
     * - 编辑定价配置时的数据预填充
     * - 配置详情页面的数据展示
     * - 复制配置功能的数据源
     *
     * @deprecated
     * @param requestDTO 查询请求，包含定价配置ID
     * @return 定价配置详情及关联的区域ID列表
     * </pre>
     */
    @PostMapping("/get-by-id")
    @ApiOperation(value = "根据ID获取定价配置详情", notes = "查询定价配置详情和关联区域，支持编辑时数据回显")
    public R<GetPricingConfigByIdResponseDTO> getById(@Valid @RequestBody BaseIdDTO requestDTO) {
        Long id = requestDTO.getId();
        log.info("按ID获取策略区域定价配置，ID：{}", id);

        StrategyAreaPricingDO pricing = strategyAreaPricingMapper.selectById(id);
        if (pricing == null) {
            return R.failed("定价配置不存在");
        }

        List<Long> areaIds = pricingAreaMappingService.getAreaIdsByPricingId(id);

        GetPricingConfigByIdResponseDTO responseDTO = GetPricingConfigByIdResponseDTO.builder()
                .id(pricing.getId())
                .strategyId(pricing.getStrategyId())
                .name(pricing.getName())
                .description(pricing.getDescription())
                .areaIds(areaIds)
                .firstWeightKg(getDouble(pricing.getFirstWeightKg()))
                .firstWeightFee(getDouble(pricing.getFirstWeightFee()))
                .additionalWeightKg(getDouble(pricing.getAdditionalWeightKg()))
                .additionalWeightFee(getDouble(pricing.getAdditionalWeightFee()))
                .volumetricWeightRatio(getDouble(pricing.getVolumetricWeightRatio()))
                .build();

        return R.success(responseDTO);
    }

    /**
     * 格式化区域名称显示
     * <pre>
     * 根据区域层级格式化区域名称的显示格式，用于前端列表展示。
     * 不同层级的格式化规则：
     * - 省级（level=1）：直接显示省份名称
     * - 市级（level=2）：省份-城市
     * - 区县级（level=3）：省份-城市-区县
     *
     * 容错处理：
     * - 处理空值和异常数据
     * - 对缺失的名称字段使用默认值
     * - 确保格式化结果的一致性
     *
     * @param area 区域实体对象
     * @return 格式化后的区域名称字符串
     * </pre>
     */
    private String formatAreaName(AreaDO area) {
        if (area == null) {
            return "未知区域";
        }

        String province = StrUtil.isNotBlank(area.getProvinceName()) ? area.getProvinceName() : "未知省份";
        switch (area.getLevel()) {
            case 1:
                return province;
            case 2:
                String city = StrUtil.isNotBlank(area.getCityName()) ? area.getCityName() : "未知城市";
                return province + "-" + city;
            case 3:
                String city02 = StrUtil.isNotBlank(area.getCityName()) ? area.getCityName() : "未知城市";
                String district = StrUtil.isNotBlank(area.getDistrictName()) ? area.getDistrictName() : "未知区县";
                return province + "-" + city02 + "-" + district;
            default:
                return "未知区域";
        }

    }

    /**
     * 获得Double
     *
     * @param bigDecimal BigDecimal
     * @return {@link Double }
     */
    private Double getDouble(BigDecimal bigDecimal) {
        return bigDecimal != null ? bigDecimal.doubleValue() : null;
    }


    /**
     * 获取BigDecimal
     *
     * @param dou double
     * @date 2025/08/31
     * @author SongRenShuo
     * @return {@link BigDecimal }
     */
    private BigDecimal getBigDecimal(Double dou) {
        return dou != null ? BigDecimal.valueOf(dou) : null;
    }
}
